package agents.machine;

import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.FSMBehaviour;
import jade.core.behaviours.OneShotBehaviour;
import jade.core.behaviours.SimpleBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.domain.FIPAException;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.lang.acl.UnreadableException;
import java.io.IOException;
import agents.manager.ManagerAgent;
import agents.utils.WorkPackage;
import agents.utils.WorkType;
import org.joda.time.DateTime;
import org.joda.time.Period;

/**
 * MachineAgent.java is parent class of all machine instances and it 
 * will also contain the common logic applicable in machine making of all types.
 * 
 * @author Group 6
 */
public class MachineAgent extends Agent {
    
    // Estados possíveis da MachineAgent
    private enum MachineState {
        /**
         * The machine is ready to work.
         */
        READY,
        /**
         * The machine is working in something.
         */
        WORKING,
        /**
         * The machine is stopping after work processing.
         */
        STOPPING
    };
    
    // Identifica o tipo de trabalho que a MachineAgent desempenha
    private WorkType workType;
    // Identifica o estado actual da MachineAgent
    private MachineState machineState;
    // Guarda o AID do manager para que a MachineAgent trabalha
    private AID managerAID;
    // Guarda a workPackage que representará o trabalho em processamento
    private WorkPackage workPackage;
    // Interface Gráfico
    private MachineAgentGUI gui;
    
    // FSM - Identificação dos diferentes estados
    private static final String FSM_ANALYZE_MANAGER_CFP = "Analyze Manager CFP";
    private static final String FSM_WAIT_FOR_A_WP = "Wait for a Work Package";
    private static final String FSM_PROCESS_WP = "Process Work Package";
    private static final String FSM_SEND_PROCESSED_WP = "Send Processed Work Package";
    private static final String FSM_CLOSE = "close";

    public MachineAgent() {
        /*
         * Devido às especificidades da classe Agent a inicialização da classe
         * dá-se no método setup e respectivos subordinados.
         */
    }

    @Override
    protected void setup() {
        super.setup();
        // Definir características da MachineAgent
        this.initAllSettings(getArguments());
        // Disponibilizar serviço da MachineAgent
        this.offerService();
        // Construir e adicionar o FSM Behavior que lida com todo o funcionamento do agente
        this.buildAndAddFSMBehavior();
    }
    
    private void initAllSettings(Object[] machineArgs) {
        // Definir o tipo de trabalho da MachineAgent
        String workType = (String) machineArgs[0];
        if (workType.equalsIgnoreCase(WorkType.CUTTING.name())) {
            this.workType = WorkType.CUTTING;
        } else if (workType.equalsIgnoreCase(WorkType.SEWING.name())) {
            this.workType = WorkType.SEWING;
        } else if (workType.equalsIgnoreCase(WorkType.PACKING.name())) {
            this.workType = WorkType.PACKING;
        }
        // Definir estado inicial da MachineAgent
        this.machineState = MachineState.READY;
        // Inicialmente, o AID da manager não se encontra definido
        this.managerAID = null;
        // Inicialmente, a MachineAgent terá que aguardar que o Manager lhe envie
        // um work package.
        this.workPackage = null;
        // Apresentar GUI e 1ª inicialização dos dados
        this.gui = new MachineAgentGUI(this);
        this.gui.setMachineLocalName(this.getLocalName());
        this.gui.setMachineWorkType(this.workType.name());
        this.gui.setMachineState(this.machineState.name());
        this.gui.updateAllInfo();
    }

    private void offerService() {
        DFAgentDescription dfd = new DFAgentDescription();
        dfd.setName(getAID());
        ServiceDescription sd = new ServiceDescription();
        sd.setType("B2B");
        sd.setName(this.workType.name() + " Machine");
        dfd.addServices(sd);
        try {
            DFService.register(this, dfd);
        } catch (FIPAException ex) {
            ex.printStackTrace();
        }
    }
    
    private void buildAndAddFSMBehavior() {
        // FSM - Criar instância
        FSMBehaviour fsm = new FSMBehaviour();
        // FSM - Primeiro estado
        fsm.registerFirstState(new AnalyzeManagerCFP(), FSM_ANALYZE_MANAGER_CFP);
        // FSM - Estados intermédios
        fsm.registerState(new WaitForAWorkPackage(), FSM_WAIT_FOR_A_WP);
        fsm.registerState(new ProcessWorkPackage(), FSM_PROCESS_WP);
        fsm.registerState(new SendProcessedWorkPackage(), FSM_SEND_PROCESSED_WP);
        // FSM - Último estado
        fsm.registerLastState(new Close(), FSM_CLOSE);
        // FSM - Definição das transições
        fsm.registerTransition(FSM_ANALYZE_MANAGER_CFP, FSM_WAIT_FOR_A_WP, 1);
        fsm.registerTransition(FSM_WAIT_FOR_A_WP, FSM_PROCESS_WP, 3);
        fsm.registerTransition(FSM_PROCESS_WP, FSM_SEND_PROCESSED_WP, 4);
        fsm.registerTransition(FSM_SEND_PROCESSED_WP, FSM_CLOSE, 0);
        fsm.registerTransition(FSM_SEND_PROCESSED_WP, FSM_ANALYZE_MANAGER_CFP, 4);
        // Adicionar o comportamento FSM ao agente
        addBehaviour(fsm);
    }
    
    private class AnalyzeManagerCFP extends SimpleBehaviour {
        
        private boolean cfpReceived;
        
        private void initData() {
            machineState = MachineState.READY;
            gui.setMachineState(machineState.name());
            gui.setMachineFSMState(FSM_ANALYZE_MANAGER_CFP);
            gui.setWpID("N/A");
            gui.setWpOrderID("N/A");
            gui.updateAllInfo();
            this.cfpReceived = false;
            workPackage = null;
            managerAID = null;
        }
        
        @Override
        public void action() {
            this.initData();
            MessageTemplate mt = MessageTemplate.MatchPerformative(ACLMessage.CFP);
            mt = MessageTemplate.and(
                    mt,
                    MessageTemplate.MatchConversationId(ManagerAgent.MSG_OFFER_WORK_PACKAGE));
            ACLMessage cfpMsg = receive(mt);
            if (cfpMsg != null) {
                this.cfpReceived = true;
                gui.appendInfoToMachineLog(FSM_ANALYZE_MANAGER_CFP + " - Received CFP");
                ACLMessage replyMsg = cfpMsg.createReply();
                replyMsg.setConversationId(ManagerAgent.MSG_OFFER_WORK_PACKAGE);
                replyMsg.setPerformative(ACLMessage.PROPOSE);
                myAgent.send(replyMsg);
            } else {
                gui.appendInfoToMachineLog(FSM_ANALYZE_MANAGER_CFP + " - Waiting");
                block();
            }
        }

        @Override
        public boolean done() {
            return this.cfpReceived;
        }

        @Override
        public int onEnd() {
            return 1;
        }
        
    }
    
    private class WaitForAWorkPackage extends SimpleBehaviour {

        private boolean requestReceived;

        private void initData() {
            machineState = MachineState.WORKING;
            gui.setMachineState(machineState.name());
            gui.setMachineFSMState(FSM_WAIT_FOR_A_WP);
            gui.setWpID("N/A");
            gui.setWpOrderID("N/A");
            gui.updateAllInfo();
            this.requestReceived = false;
            workPackage = null;
            managerAID = null;
        }
        
        @Override
        public void action() {
            this.initData();
            MessageTemplate mt = MessageTemplate.MatchPerformative(ACLMessage.REQUEST);
            mt = MessageTemplate.and(
                    mt,
                    MessageTemplate.MatchConversationId(ManagerAgent.MSG_SEND_WORK_PACKAGE));
            ACLMessage requestMsg = receive(mt);
            if (requestMsg != null) {
                this.requestReceived = true;
                try {
                    workPackage = (WorkPackage)(requestMsg.getContentObject());
                    managerAID = requestMsg.getSender();
                    gui.appendInfoToMachineLog(FSM_WAIT_FOR_A_WP + " - Received WP - ID: " + workPackage.getId());
                } catch (UnreadableException ex) {
                    ex.printStackTrace();
                }
            } else {
                gui.appendInfoToMachineLog(FSM_WAIT_FOR_A_WP + " - Waiting");
                block();
            }
        }

        @Override
        public boolean done() {
            return this.requestReceived;
        }

        @Override
        public int onEnd() {
            return 3;
        }
        
    }
    
    private class ProcessWorkPackage extends OneShotBehaviour {

        // Duração do processamento em segundos
        private int processingDuration;
        
        private void initData() {
            gui.setMachineFSMState(FSM_PROCESS_WP);
            gui.setWpID(Integer.toString(workPackage.getId()));
            gui.setWpOrderID(Integer.toString(workPackage.getOrderID()));
            gui.updateAllInfo();
        }
        
        @Override
        public void action() {
            this.initData();
            gui.appendInfoToMachineLog(FSM_PROCESS_WP
                    + " - ID: "
                    + workPackage.getId()
                    + " - Started!");
            DateTime processingStarted = new DateTime();
            doWait(workPackage.getWorkType().getDefaultTimeToProcess());
            DateTime processingEnded = new DateTime();
            Period duration = new Period(processingStarted, processingEnded);
            this.processingDuration = duration.getSeconds();
        }

        @Override
        public int onEnd() {
            gui.appendInfoToMachineLog(FSM_PROCESS_WP
                    + " - ID: "
                    + workPackage.getId()
                    + " - Finished! - Took: "
                    + Integer.toString(this.processingDuration)
                    + " seconds!");
            return 4;
        }
        
    }
   
    private class SendProcessedWorkPackage extends OneShotBehaviour {

        @Override
        public void action() {
            ACLMessage sendWP = new ACLMessage(ACLMessage.INFORM);
            sendWP.addReceiver(managerAID);
            sendWP.setConversationId(ManagerAgent.MSG_RECEIVE_PROCESSED_WORK_PACKAGE);
            try {
                sendWP.setContentObject(workPackage);
                myAgent.send(sendWP);
                gui.appendInfoToMachineLog(FSM_SEND_PROCESSED_WP
                    + " - WP Sent - ID: " + workPackage.getId());
            } catch (IOException ex) {
                gui.appendInfoToMachineLog(FSM_SEND_PROCESSED_WP
                    + " - WP Sent Error - ID: " + workPackage.getId());
                ex.printStackTrace();
            }
        }

        @Override
        public int onEnd() {
            return (machineState == MachineState.STOPPING) ? 0 : 4;
        }
        
    }
            
    private class Close extends OneShotBehaviour {

        @Override
        public void action() {
            doDelete();
        }
        
    }
    
    @Override
    protected void takeDown() {
        super.takeDown();
        // Remover announcements
        try {
            DFService.deregister(this);
        }
        catch (FIPAException ex) {
            ex.printStackTrace();
        }
    }
    
    public void shutdownAgentAndGUI() {
        this.gui.setVisible(false);
        this.gui.dispose();
        this.gui = null;
        this.doDelete();
    }
    
}
